#include <hash_map>

#include "../DictionaryBuilders/TermDictBuilder.h"
#include "../Indexer/AuthorIndexer.h"

bool AuthorIndex::instanceFlag = false;
AuthorIndex* AuthorIndex::builderInstance = NULL;

AuthorIndex* AuthorIndex::getInstance() {
	if (!instanceFlag) {
		builderInstance = new AuthorIndex();
		return builderInstance;
	}
	return builderInstance;
}

AuthorIndex::AuthorIndex() {
		instanceFlag = true;
		configInstance = config::getInstance();
		CUtilities::string_to_number(barrelNumber, configInstance->getValue("barrel_number"));

		authorMap = new AUTHORINDEXMAP[barrelNumber];
		postingListCounter = new int[barrelNumber];
		barrelStream = new ofstream[barrelNumber];
		barrelCounter = new int[barrelNumber];

		setBarrelNumberToMap();
		dirPath = configInstance->getValue("path_to_index_store_dir");
		for (int i = 0; i < barrelNumber; i++) {
			postingListCounter[i] = 0;
			barrelCounter[i] = 0;
			barrelStream[i].open(
					(dirPath + "/AuthorIndex/index.inv.00"
							+ CUtilities::number_to_string(i) + ".000.txt").c_str(),
					ios::out);
			if (!barrelStream[i].good()) {
				cout
						<< dirPath + "/AuthorIndex/index.inv.00"
						+ CUtilities::number_to_string(i) + ".000.txt" << "fail open!!!!" << endl;
			}
		}
	}

void AuthorIndex::writeIndexToFile() {
	AUTHORINDEXMAP::iterator it;
	for (it = authorMap[currentBarreal].begin();
			it != authorMap[currentBarreal].end(); it++) {
		barrelStream[currentBarreal] << it->first << ":";
		map<size_t, AuthorIndexValue>::iterator subIt;
		for (subIt = it->second.begin(); subIt != it->second.end(); subIt++) {
			barrelStream[currentBarreal] << subIt->first << "|"
					<< subIt->second.count << ",";
		}
		barrelStream[currentBarreal] << "$";
	}
	barrelStream[currentBarreal] << endl;

//	INVERTEDINDEXMAP::iterator it;
//	for (it = authorMap[currentBarreal].begin(); it != authorMap[currentBarreal].end(); it++) {
//		barrelStream[currentBarreal] << it->first << ":" << it->second << "$";
//	}
}

void AuthorIndex::flushInstance() {
//	for(int i=0; i<barrelNumber; i++){
//		cout << "barrel "<< i << " size: " << postingListCounter[i] << endl;
//	}
	for (int i = 0; i < barrelNumber; i++) {
		if (postingListCounter[i] >= barrelSize) {
			currentBarreal = i;
			writeIndexToFile();
			barrelStream[i].close();
			barrelCounter[i]++;
			authorMap[i].clear();
			postingListCounter[i] = 0;
			barrelStream[i].open(
					(dirPath + "/AuthorIndex/index.inv.00"
							+ CUtilities::number_to_string(i) + ".00"
							+ CUtilities::number_to_string(barrelCounter[i])
							+ ".txt").c_str(), ios::out);
		}
	}
}

//Parameters: author id, file id, term string
void AuthorIndex::addAuthorPostingList(size_t tid, size_t fid, string term) {
	setCurrentBarrel(term);
	postingListCounter[currentBarreal]++;
	pair<AUTHORINDEXMAP::iterator, bool> ret;
	AuthorIndexValue value(1);
	map<size_t, AuthorIndexValue> submap;
	submap.insert(make_pair(fid, value));
	ret = authorMap[currentBarreal].insert(make_pair(tid, submap));
	if (!ret.second) { //term id exist
		pair<map<size_t, AuthorIndexValue>::iterator, bool> subRet;
		subRet = ret.first->second.insert(make_pair(fid, value));
		if (!subRet.second) { //fid id exist in postinglist
			subRet.first->second.count++;
		}
	}
}

void AuthorIndex::setCurrentBarrel(string term) {
	if (!isalpha(term[0])) {
		currentBarreal = 0;
	} else {
		currentBarreal = barrelMapper[term[0]];
	}
}

void AuthorIndex::closeAll() {
	for (int i = 0; i < barrelNumber; i++) {
		currentBarreal = i;
		if (authorMap[i].size() > 0) {
			writeIndexToFile();
			barrelStream[i].close();
			authorMap[i].clear();
		}
	}
}

void AuthorIndex::printMAP() {
}

void AuthorIndex::loadBarrelFile(int _barrelNumber, map<int, string>& container) {
	int fileNumber = 0;
	string filePath = dirPath + "/AuthorIndex/index.inv.00"
			+ CUtilities::number_to_string(_barrelNumber) + ".00"
			+ CUtilities::number_to_string(fileNumber) + ".txt";
	string content;
	ifstream currentFile;
	currentFile.open(filePath.c_str());

	while (currentFile.is_open()) { //check file exist
		//start load file
		while (!currentFile.eof()) {
			getline(currentFile, content, '$');
			if (content.length() > 0) {
				int position = content.find(":");
				if (position != string::npos) {
					int key;
					CUtilities::string_to_number(key, content.substr(0, position));
					string value = content.substr(position + 1, content.length());
					map<int, string>::iterator finder = container.find(key);
					if (finder != container.end()) {
						finder->second = finder->second + value;
					} else {
						container[key] = value;
					}
				}
			}
		}
		currentFile.close();
		remove(filePath.c_str());
		fileNumber++;
		filePath = dirPath + "/AuthorIndex/index.inv.00"
				+ CUtilities::number_to_string(_barrelNumber) + ".00"
				+ CUtilities::number_to_string(fileNumber) + ".txt";
		currentFile.open(filePath.c_str());
	}
}

void AuthorIndex::mergeIndex() {
	for (int i = 0; i < barrelNumber; i++) {
		map<int, string> postingList;
		loadBarrelFile(i, postingList);
		//finish read one barral, then write the final index file
		ofstream writeIndex;
		ofstream writePostingList;
		writeIndex.open(
				(dirPath + "/AuthorIndex/index.inv.00"
						+ CUtilities::number_to_string(i) + ".txt").c_str(),
				ios::out);
		writePostingList.open(
				(dirPath + "/AuthorIndex/index.inv.00"
						+ CUtilities::number_to_string(i) + ".pstngs.txt").c_str(),
				ios::out);
		map<int, string>::iterator it = postingList.begin();
		for (it; it != postingList.end(); it++) {
			long pos = writePostingList.tellp();
			writePostingList << it->second << "$";
			writeIndex << it->first << " " << pos << endl;
		}
		writeIndex.close();
		writePostingList.close();
	}

}

void AuthorIndex::setBarrelNumberToMap() {
	int barrelSpan = 26 / barrelNumber;
	int reminder = (26 % barrelNumber);
	//map char to barrel number
	int count = 0;
	int j = 0;
	for (int i = 97; i < 123; i++) {
		barrelMapper.insert(make_pair((char) i, j));
		count++;
		if (count == barrelSpan) {
			if (reminder > 0) {
				barrelMapper.insert(make_pair((char) ++i, j));
				reminder--;
			}
			count = 0;
			j++;
		}
	}
}
